/*
 * Copyright (c) 2007, TopCoder, Inc. All rights reserved.
 */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using NHibernate;
using Studentium;
using Studentium.UIElements;
using TopCoder.Util.Calendar.Entry;

namespace TopCoder.WinForms.Controls.CalendarView.Views
{
    /// <summary>
    /// <para>This class draws the calendar data for a single day to the drawing panel. This class handles one
    /// day at a time, so the next and previous buttons only move forward and back one day at a time.</para>
    /// </summary>
    /// <remarks>
    /// Thread Safety: This class itself is mutable, as it inherits the mutability of its parent class.
    /// </remarks>
    /// <author>Ghostar</author>
    /// <author>yext</author>
    /// <author>argolite</author>
    /// <author>alex.dolin</author>
    /// <version>1.1</version>
    /// <copyright>Copyright (c) 2007, TopCoder, Inc. All rights reserved.</copyright>
    public class DailyCalendarView : AbstractCalendarView
    {
        /// <summary>
        /// Represents the header text of the "Description" column in this view.
        /// </summary>
        private const string DESCRIPTION_STRING = "Class name";

        /// <summary>
        /// Represents the header text of the "Start Date" column in this view.
        /// </summary>
        private const string DATE_STRING = "Start time";

        private const string FULL_NAME = "Class Name";

        private const string ATTENDANCE = "Attendances";

        /// <summary>
        /// Represents the header text of the "Priority" column in this view.
        /// </summary>
        private const string PRIORITY_STRING = "Priority";

        /// <summary>
        /// The format string used to display in the "Start Date" column.
        /// </summary>
        private const string DATE_FORMAT_STRING = "d";

        /// <summary>
        /// The left margin for text.
        /// </summary>
        internal const int PADDING_LEFT = 10;

        /// <summary>
        /// The right margin for text.
        /// </summary>
        internal const int PADDING_RIGHT = 10;

        /// <summary>
        /// The top margin for text.
        /// </summary>
        internal const int PADDING_TOP = 5;

        /// <summary>
        /// The bottom margin for text.
        /// </summary>
        internal const int PADDING_BOTTOM = 5;

        /// <summary>
        /// Default list of columns. Used when Columns property is null.
        /// </summary>
        internal static readonly IList<string> defaultColumns =
            new string[] {DATE_STRING, FULL_NAME, ATTENDANCE};

        /// <summary>
        /// The font for the colored type rows. (e.g. "Business", "Home")
        /// </summary>
        internal readonly static Font typeRowFont = new Font("Arial", 10F, FontStyle.Bold);

        /// <summary>
        /// The font for the header rows. ("Start Date" and "Description")
        /// </summary>
        internal readonly static Font headerFont = new Font("Arial", 10F, FontStyle.Bold);

        /// <summary>
        /// The font for the items shown in this view.
        /// </summary>
        internal readonly static Font itemFont = new Font("Arial", 10F);

        /// <summary>
        /// The brush setting for the text of the type rows.
        /// </summary>
        internal readonly static Brush typeRowBrush = new SolidBrush(Color.Black);

        /// <summary>
        /// The brush setting for the text of the header rows.
        /// </summary>
        internal readonly static Brush headerBrush = new SolidBrush(Color.Black);

        /// <summary>
        /// The brush setting for the background of the header rows.
        /// </summary>
        internal readonly static Brush headerBackBrush = new SolidBrush(Color.LightGray);

        /// <summary>
        /// The brush setting for the text of the items.
        /// </summary>
        internal readonly static Brush itemBrush = new SolidBrush(Color.Black);

        /// <summary>
        /// The pen setting for the border of the cells.
        /// </summary>
        internal readonly static Pen itemBorderPen = new Pen(Color.LightGray, 1F);

        /// <summary>
        /// <para>Represents the calendar entries sorted by type.</para>
        /// </summary>
        private IDictionary<CalendarEntryType, IList<CalendarEntry>> sortedEntries;

        /// <summary>
        /// Represents the names of columns to display. A null, by default, means all available columns will be used
        /// as they are found in the entry. An empty list means no columns will be displayed, and any given list will
        /// indicate the desired columns. If a given column is not found in the entry, it is ignored.
        /// The list is managed in the Columns property.
        /// </summary>
        private IList<string> columns;

        /// <summary>
        /// <para>Use the specified date to filter the calendar entries.</para>
        /// </summary>
        private void FilterData()
        {
            // Set the label text.
            TitleLabelText = DateOfInterest.ToString("dddd dd MMMM yyyy");

            // Select the current data.
            CurrentData = Helper.SelectEntriesByDateRange(AllData, DateOfInterest.Date, DateOfInterest.Date);

            // Sort the entries by type.
            sortedEntries = Helper.GroupEntriesByType(CurrentData);

            // Invalidate the drawing panel, so the panel will be painted with new data.
            DrawingPanel.Invalidate();
        }

        /// <summary>
        /// Represents the date around which this view should be built. The resulting view must inlcude this date.
        /// By default, the current date is used.
        /// </summary>
        /// <value>The date around which this view should be built.</value>
        public override DateTime DateOfInterest
        {
            set
            {
                base.DateOfInterest = value;
                FilterData();
            }
        }

        /// <summary>
        /// <para>Gets string representation of the calendar entry property by the
        /// name of the column in which this property is displayed.</para>
        /// <para>Returns empty string if the name of the column is not recognized
        /// or <paramref name="entry"/> is <c>null</c>.</para>
        /// </summary>
        /// <param name="entry">The entry which property to get.</param>
        /// <param name="column">The name of the column in which property of interest is displayed.</param>
        /// <returns>String representation of the calendar entry property.</returns>
        internal static string GetColumnValue(CalendarEntry entry, string column)
        {
            if(entry == null)
            {
                return "";
            }

            switch(column)
            {
                case DESCRIPTION_STRING:
                    return entry.Description;
                case PRIORITY_STRING:
                    return entry.Priority.ToString();
                case DATE_STRING:
                    return entry.Date.ToShortTimeString();
                         //entry.Date.ToString(DATE_FORMAT_STRING);
                case FULL_NAME:
                    return entry.FullName;
                case ATTENDANCE:
                    return MainForm.Instance.GetAttendancesNumber(entry).ToString();
                default:
                    return "";
            }
        }

        /// <summary>
        /// <para>This method draws the drawing panel with the current calendar entries.</para>
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event, containing the graphic.</param>
        protected override void OnDrawingPanelPaint(object sender, PaintEventArgs e)
        {
            if (DrawingPanel.Controls.Count > 0)
            {
                // Remove old popup panels,
                // invalidate the drawing panel (so this method will be called again),
                // then return
                Helper.ReleaseChildControls(DrawingPanel);
                DrawingPanel.Invalidate();
                return;
            }

            IList<string> viewColumns;
            if(columns == null)
            {
                viewColumns = defaultColumns;
            }
            else
            {
                viewColumns = columns;
            }

            // The target.
            Graphics g = e.Graphics;

            #region Measurements:

            float headerRowHeight = 0;
            float[] columnsWidth = new float[viewColumns.Count];
            IDictionary<CalendarEntry, float[]> entryColumnsWidth =
                new Dictionary<CalendarEntry, float[]>(CurrentData.Count);

            // Calculate the size of the header row.
            for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
            {
                SizeF cellSize = g.MeasureString(viewColumns[columnIndex], headerFont);
                columnsWidth[columnIndex] = cellSize.Width;
                headerRowHeight = Math.Max(headerRowHeight, cellSize.Height);
            }

            float measuredWidth = 0;
            float measuredHeight = 0;
            int rowCount = 0;
            int headerRowCount = 0;
            foreach (CalendarEntryType entryType in sortedEntries.Keys)
            {
                // Measure the size of the colored type row.
                SizeF typeRowSize = g.MeasureString(entryType.Description, typeRowFont);

                measuredWidth = Math.Max(typeRowSize.Width, measuredWidth);
                measuredHeight = Math.Max(measuredHeight, typeRowSize.Height);
                headerRowCount += 1;
                rowCount += 1;

                foreach (CalendarEntry entry in sortedEntries[entryType])
                {
                    rowCount++;
                    float[] currentColumnsWidth = new float[columnsWidth.Length];

                    // Measure the size of the item cells.
                    for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                    {
                        SizeF cellSize = g.MeasureString(GetColumnValue(entry, viewColumns[columnIndex]), itemFont);

                        currentColumnsWidth[columnIndex] = cellSize.Width;
                        columnsWidth[columnIndex] = Math.Max(columnsWidth[columnIndex], cellSize.Width);
                        measuredHeight = Math.Max(measuredHeight, cellSize.Height);
                    }

                    entryColumnsWidth[entry] = currentColumnsWidth;
                }
            }

            float columnsWidthSum = 0;
            foreach (float width in columnsWidth)
            {
                columnsWidthSum += width;
            }

            measuredWidth = Math.Max(measuredWidth, columnsWidthSum);

            // Add the paddings:
            measuredWidth += (PADDING_LEFT + PADDING_RIGHT) * viewColumns.Count;
            measuredHeight += PADDING_TOP + PADDING_BOTTOM;
            headerRowHeight += PADDING_TOP + PADDING_BOTTOM;

            float totalHeight = measuredHeight * rowCount + headerRowHeight * headerRowCount;

            // Adjust the size of the drawing panel.
            Helper.AdjustPanelSize(HolderPanel, DrawingPanel,
                                   (int)Math.Ceiling(measuredWidth), (int)Math.Ceiling(totalHeight),
                                   ColumnWidthRendering);

            // Add or remove proportional amount of width for each column
            for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
            {
                columnsWidth[columnIndex] +=
                    (DrawingPanel.Width - measuredWidth)*(columnsWidth[columnIndex]/columnsWidthSum);
            }

            measuredWidth = DrawingPanel.Width;

            #endregion
            #region Draw the panel:

            RectangleF cellRect = new RectangleF();
            cellRect.Y = PADDING_TOP;

            foreach (CalendarEntryType entryType in sortedEntries.Keys)
            {
                // The "Type" row:
                // The background color:
                g.FillRectangle(new SolidBrush(GetColor(entryType)), 0,
                    cellRect.Y - PADDING_TOP, measuredWidth, measuredHeight);
                // The string:
                cellRect.X = PADDING_LEFT;
                cellRect.Width = measuredWidth - PADDING_RIGHT;
                cellRect.Height = measuredHeight - PADDING_BOTTOM;
                g.DrawString(entryType.Description, typeRowFont, typeRowBrush, cellRect, Helper.StringTrimFormat);
                cellRect.Y += measuredHeight;

                // The header row:
                // The background color:
                g.FillRectangle(headerBackBrush, 0, cellRect.Y - PADDING_TOP, measuredWidth, headerRowHeight);
                // The header strings:
                cellRect.X = PADDING_LEFT;
                cellRect.Height = headerRowHeight - PADDING_BOTTOM;
                for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                {
                    cellRect.Width = columnsWidth[columnIndex];
                    g.DrawString(viewColumns[columnIndex], headerFont, headerBrush, cellRect, Helper.StringTrimFormat);
                    cellRect.X += columnsWidth[columnIndex] + PADDING_RIGHT + PADDING_LEFT;
                }
                cellRect.Y += headerRowHeight;

                // The entries rows:
                cellRect.X = PADDING_LEFT;
                cellRect.Height = measuredHeight - PADDING_BOTTOM;
                foreach (CalendarEntry entry in sortedEntries[entryType])
                {
                    float y = cellRect.Y;
                    cellRect.Y = PADDING_TOP;

                    bool truncated = false;
                    for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                    {
                        if(entryColumnsWidth[entry][columnIndex] > columnsWidth[columnIndex])
                        {
                            truncated = true;
                            break;
                        }
                    }

                    // Create a new popup panel for calendar entry
                    FullEntryPopupPanel entryPanel = new FullEntryPopupPanel(entry, truncated, this, null, entry.Date);
                    entryPanel.Columns = viewColumns;
                    entryPanel.Left = 0;
                    entryPanel.Top = (int)Math.Ceiling(y - PADDING_TOP);
                    entryPanel.Width = (int)Math.Ceiling(measuredWidth);
                    entryPanel.Height = (int)Math.Ceiling(measuredHeight);
                    entryPanel.Parent = DrawingPanel;
                    entryPanel.Paint +=
                        new EntryPaintHandler(entry, viewColumns, columnsWidth, measuredHeight).OnPaint;                    
                    entryPanel.Click += new EventHandler(entryPanel_Click);

                    cellRect.Y = y + measuredHeight;
                }
            }
            #endregion
        }        

        public void entryPanel_Click(object sender, EventArgs e)
        {
            EditScheduleEntry form = new EditScheduleEntry(((FullEntryPopupPanel)sender).Entry, Parent.MainController, false);
            form.Show(Parent);
        }

        /// <summary>
        /// Creates a new instance of this class.
        /// </summary>
        /// <param name="parent">The parent calendar view control.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="parent"/> is null.</exception>
        public DailyCalendarView(CalendarViewControl parent)
            : base(parent)
        {
            FilterData();
        }

        /// <summary>
        /// Represents the names of columns to display. A null, by default, means all available columns will be used
        /// as they are found in the entry. An empty list means no columns will be displayed, and any given list will
        /// indicate the desired columns. If a given column is not found in the entry, it is ignored.
        /// </summary>
        /// <value>The names of columns to display.</value>
        /// <exception cref="ArgumentException">If set to a list with null/empty strings.</exception>
        public IList<string> Columns
        {
            get
            {
                return Helper.GetStringListCopy(columns);
            }
            set
            {
                Helper.CheckStringListForNullAndEmptyElements(value, "Columns");
                columns = Helper.GetStringListCopy(value);
            }
        }

        /// <summary>
        /// <para>This method is called when the "Next" button is pressed on the header of the view.
        /// This method should get all calendar data for the day that is after the current day being shown.
        /// </para>
        /// </summary>
        /// <param name="sender">The next button that was pressed.</param>
        /// <param name="e">The event args.</param>
        protected override void OnNextButtonClick(object sender, EventArgs e)
        {
            DateOfInterest += new TimeSpan(1, 0, 0, 0);
        }

        /// <summary>
        /// <para>This method is called when the "Previous" button is pressed on the header of the view.
        /// This method should get all calendar data for the day that is before the current day being shown.
        /// </para>
        /// </summary>
        /// <param name="sender">The previous button that was clicked.</param>
        /// <param name="e">The event args.</param>
        protected override void OnPreviousButtonClick(object sender, EventArgs e)
        {
            DateOfInterest -= new TimeSpan(1, 0, 0, 0);
        }

        /// <summary>
        /// Private helper class to draw one entry.
        /// </summary>
        /// <remarks>Thread Safety: This class is immutable and thread-safe.</remarks>
        private class EntryPaintHandler
        {
            /// <summary>
            /// The calendar entry instance which holds data to display.
            /// </summary>
            private readonly CalendarEntry entry;

            /// <summary>
            /// List of calendar entry columns to display.
            /// </summary>
            private readonly IList<string> columns;

            /// <summary>
            /// Measured width of each column.
            /// </summary>
            private readonly float[] columnsWidth;

            /// <summary>
            /// The height of one row.
            /// </summary>
            private readonly float height;

            /// <summary>
            /// Creates a new instance of the class.
            /// </summary>
            /// <param name="entry">The calendar entry instance which holds data to display.</param>
            /// <param name="columns">List of calendar entry columns to display.</param>
            /// <param name="columnsWidth">Measured width of each column.</param>
            /// <param name="height">The height of one row.</param>
            public EntryPaintHandler(CalendarEntry entry, IList<string> columns, float[] columnsWidth, float height)
            {
                this.entry = entry;
                this.columns = columns;
                this.columnsWidth = columnsWidth;
                this.height = height;
            }

            /// <summary>
            /// Processes <see cref="Control.Paint"/> event for one entry panel.
            /// </summary>
            /// <param name="sender">The sender of the event.</param>
            /// <param name="e">The event, containing the graphic.</param>
            public void OnPaint(object sender, PaintEventArgs e)
            {
                Graphics g = e.Graphics;

                // The rectangle to hold one cell.
                // String will be truncated if it's not fitting in the rectangle.
                RectangleF cellRect = new RectangleF(PADDING_LEFT, PADDING_TOP, 0, height);
                for (int columnIndex = 0; columnIndex < columnsWidth.Length; columnIndex++)
                {
                    cellRect.Width = columnsWidth[columnIndex];

                    // Border
                    g.DrawRectangle(itemBorderPen,
                                    cellRect.X - PADDING_LEFT, 0,
                                    cellRect.Width + PADDING_LEFT + PADDING_RIGHT,
                                    height - 1);

                    // Value of the calendar entry column
                    g.DrawString(GetColumnValue(entry, columns[columnIndex]), itemFont,
                                 itemBrush, cellRect, Helper.StringTrimFormat);

                    cellRect.X += columnsWidth[columnIndex] + PADDING_RIGHT + PADDING_LEFT;
                }
            }
        }
    }
}
